#pragma once

#include <iostream>
#include <string>

#include <filesystem> //文件读写
#include <fstream>
#include <sstream>
#include <sys/types.h>
#include <unistd.h>

#include <ctime>
#include "Mutex.hpp"

namespace LogModule
{
    using namespace MutexModule;
    const std::string gsep = "\r\n"; // 分隔符
    class LogStrategy
    {
    public:
        ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 通过继承，重写刷新策略，这里是刷新到显示器
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy() {}
        virtual void SyncLog(const std::string &message) override
        {
            MutexGuard Mutex(&_mutex);
            std::cout << message << gsep;
        }
        ~ConsoleLogStrategy() {}

    private:
        Mutex _mutex;
    };

    // 依然是刷新策略，这里就刷新到文件里
    const std::string defaultpath = "./log";
    const std::string defaultfile = "my.log"; // 默认在当前路径下创建my.log文件
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
            : _path(path),
              _file(file)
        {
            if (std::filesystem::exists(_path)) // 如果path已经存在了，之间返回就行了
            {
                return;
            }
            else
            {
                try
                {
                    std::filesystem::create_directories(_path); // 如果不存在就新建，失败了就捕捉一下
                }
                catch (const std::filesystem::filesystem_error &e)
                {
                    std::cerr << e.what() << "\n"; // 还是用cerr
                }
            }
        }
        virtual void SyncLog(const std::string &message) override
        {
            std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
            std::ofstream out(filename, std::ios::app); // 以追加的方式打开
            if (!out.is_open())                         // 如果打开了
            {
                return;
            }
            out << message << gsep;
            out.close();
        }
        ~FileLogStrategy() {}

    private:
        std::string _path;
        std::string _file;

        Mutex _Mutex;
    };

    // 到这里有两个刷新策略，显示器和文件

    // 需要当前的日志等级，日志时间
    enum class LogLevel // 第一个
    {
        DEBUG,
        FATAL,
        ERROR,
        WRNNING,
    };
    // 因为要打印出来，转换成字符串的形式
    std::string LevelToString(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::FATAL:
            return "FATAL";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::WRNNING:
            return "WRNNING";
        default:
            return "UNKNOWN";
        }
    }
    std::string GetCurTime()
    {
        time_t cur = time(nullptr);
        struct tm cur_tm;
        localtime_r(&cur, &cur_tm); // cur_tm结构体里面就是我们系统的当前时间
        char buffer[64];
        snprintf(buffer, sizeof(buffer), "%4d-%2d-%2d %d:%d:%d",
                 cur_tm.tm_year + 1900,
                 cur_tm.tm_mon + 1,
                 cur_tm.tm_mday,
                 cur_tm.tm_hour,
                 cur_tm.tm_min,
                 cur_tm.tm_sec);
        return buffer;
    }
    // 日志类
    // 需要方法知道往哪里去打印，内部成员变量刷新策略，继承
    class Logger
    {
    public:
        Logger()
        {
            EnableConsoleLogStrtegy(); // 默认的话就往显示器打印
        }
        void EnableFileLogStrtegy()
        {
            _fflush_strtegy = std::make_unique<FileLogStrategy>();
        }
        void EnableConsoleLogStrtegy()
        {
            _fflush_strtegy = std::make_unique<ConsoleLogStrategy>();
        }
        // 单独的表示一条日志信息
        class LogMessage // 内部类
        {
        public:
            //[2025-05-5 17:13:59] [DEBUG] [11519] [TcpServer.hpp] [31] - access success...223.104.107.24:47290
            // 需要日志等级，行号，文件名，自身
            LogMessage(LogLevel &level, std::string &filename, int line, Logger &logger)
                : _cur_time(GetCurTime()),
                  _loglevel(level),
                  _pid(getpid()),
                  _filename(filename),
                  _line_num(line),
                  _logger(logger)
            {
                std::stringstream ss; // 用stringstream拼接字符串
                ss << "[" << _cur_time << "] "
                   << "[" << LevelToString(_loglevel) << "] "
                   << "[" << _pid << "] "
                   << "[" << _filename << "] "
                   << "[" << _line_num << "]"
                   << "- ";
                _log_info = ss.str(); // 写入到日志信息，直接打印这个
            }
            // 右半部分
            //- access success...223.104.107.24:47290
            template <class T> // 用模版，进行任意类型的拼接
            // 这里用自身类的引用也是这个原因，因为要拼接，一定要返回自己
            LogMessage &operator<<(const T &message) // 重载<<,我们就可以在外部使用<<，进行任意类型的拼接
                                                     // 比如LogMessage << 1 << "hello world"
            {
                std::stringstream ss;
                ss << message;
                _log_info += ss.str();
                return *this; // 返回自己
            }
            // 析构的时候去执行刷新策略，因为是内部类，可以访问
            ~LogMessage()
            {
                if (_logger._fflush_strtegy)
                {
                    _logger._fflush_strtegy->SyncLog(_log_info); // 指向刷新策略
                }
            }

        private:
            // 需要的是当前时间，日志等级，pid，当前文件，行号，信息
            std::string _cur_time;
            LogLevel _loglevel;
            pid_t _pid;
            std::string _filename;
            int _line_num;
            std::string _log_info;
            Logger &_logger;
        };
        // 仿函数，实现LogMessage << 1 << 3.01 << "hello world"
        // 只有完全走到底，才会去析构
        LogMessage operator()(LogLevel level, std::string filename, int line)
        {
            return LogMessage(level, filename, line, *this);
        }
        ~Logger() {}

    private:
        std::unique_ptr<LogStrategy> _fflush_strtegy;
    };
    // 全局日志对象
    Logger logger;
// log(LogLevel::DEBUG,main.cc,10)<<"hello world";

// 用宏来简化用户操作，获取文件名和行号
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Console_Log_Strtegy() logger.EnableConsoleLogStrtegy()
#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
}
